BookMyShow
Table of Contents
- Requirements Clarification
- Core Entities & Relationships
- Class Design
- UML Class Diagram
- Implementation
- Design Patterns & Best Practices
- Concurrency & Seat Locking
- Edge Cases & Extensions
- Summary
Requirements Clarification
Functional Requirements
Core Features
- User Management: Registration, login, profile management
- Movie Browsing: Search and filter movies by city, genre, language
- Theater Management: View theaters showing specific movies
- Show Management: View showtimes for movies at different theaters
- Seat Selection: Browse available seats, select seats with layout visualization
- Booking: Reserve seats and create bookings
- Payment Processing: Process payments for bookings
- Ticket Generation: Generate tickets after successful payment
- Booking History: View past and upcoming bookings
- Cancellation: Cancel bookings with refund processing
Admin Features
- Add/update/remove movies
- Add/update/remove theaters and screens
- Add/update/remove shows
- Manage seat layouts
- View booking analytics
Non-Functional Requirements
- Concurrency: Handle simultaneous booking requests (prevent double booking)
- Consistency: Ensure seat locking mechanism is reliable
- Performance: Fast search and booking (< 3 seconds)
- Scalability: Support millions of users and thousands of concurrent bookings
- Availability: 99.9% uptime
- Security: Secure payment processing and user data
Capacity Estimates
- Users: 10 million registered users
- Daily Active Users: 500,000
- Cities: 100 cities
- Theaters: 2,000 theaters
- Screens per Theater: Average 5 screens
- Shows per Screen: 4-5 shows per day
- Seats per Screen: Average 200 seats
- Daily Bookings: 100,000 bookings
Scope Boundaries
Included
- Movie and show management
- Seat selection with locking
- Booking and payment flow
- Search and filter functionality
- Cancellation and refunds
- User authentication
Excluded
- Food and beverage ordering
- Loyalty programs
- Real-time notifications (email/SMS)
- Movie ratings and reviews
- Streaming integration
- Physical ticket printing
Core Entities & Relationships
Main Entities
- User: Customers who book tickets
- Movie: Film information (title, genre, duration, language)
- Theater: Physical cinema location
- Screen: Auditorium within a theater
- Show: Specific screening of a movie in a screen at a time
- Seat: Individual seat in a screen with row/column
- Booking: Reservation made by a user
- Payment: Payment transaction for a booking
- Ticket: Confirmation ticket after successful payment
- City: Geographic location for theaters
Key Relationships
- City HAS-MANY Theater (One-to-Many)
- Theater HAS-MANY Screen (One-to-Many)
- Screen HAS-MANY Seat (One-to-Many)
- Screen HAS-MANY Show (One-to-Many)
- Show BELONGS-TO Movie (Many-to-One)
- Show BELONGS-TO Screen (Many-to-One)
- User HAS-MANY Booking (One-to-Many)
- Booking HAS-MANY Seat (Many-to-Many through ShowSeat)
- Booking HAS-ONE Payment (One-to-One)
- Booking GENERATES Ticket (One-to-Many)
Class Design
Core Classes with Responsibilities
User Management
- User: Store user details, manage authentication
- UserType: Enum for customer/admin roles
Movie & Theater
- Movie: Store movie metadata
- Theater: Manage theater details and screens
- Screen: Manage screen layout and seats
- City: Store location information
Show & Booking
- Show: Represent movie screenings with timing
- Seat: Represent physical seats with type
- ShowSeat: Track seat availability for specific shows
- Booking: Manage reservation details
- BookingStatus: Enum for booking states
Payment & Tickets
- Payment: Process and track payments
- PaymentStatus: Enum for payment states
- Ticket: Generate confirmation tickets
Services
- MovieService: Handle movie operations
- TheaterService: Manage theater operations
- BookingService: Handle booking workflow
- PaymentService: Process payments
- SeatLockManager: Manage seat locking for concurrency
UML Class Diagram
Implementation
Enums
UserType.java
public enum UserType {
CUSTOMER,
ADMIN,
THEATER_OWNER
}
Genre.java
public enum Genre {
ACTION,
COMEDY,
DRAMA,
HORROR,
THRILLER,
ROMANCE,
SCI_FI,
ANIMATION,
DOCUMENTARY
}
Language.java
public enum Language {
ENGLISH,
HINDI,
TAMIL,
TELUGU,
MALAYALAM,
KANNADA,
BENGALI,
MARATHI
}
SeatType.java
public enum SeatType {
REGULAR(100.0),
PREMIUM(150.0),
VIP(200.0),
RECLINER(300.0);
private final double basePrice;
SeatType(double basePrice) {
this.basePrice = basePrice;
}
public double getBasePrice() {
return basePrice;
}
}
ShowSeatStatus.java
public enum ShowSeatStatus {
AVAILABLE,
LOCKED,
BOOKED
}
BookingStatus.java
public enum BookingStatus {
PENDING,
CONFIRMED,
CANCELLED,
EXPIRED
}
PaymentStatus.java
public enum PaymentStatus {
PENDING,
SUCCESS,
FAILED,
REFUNDED
}
PaymentMethod.java
public enum PaymentMethod {
CREDIT_CARD,
DEBIT_CARD,
UPI,
NET_BANKING,
WALLET
}
Core Entity Classes
User.java
import java.util.ArrayList;
import java.util.List;
public class User {
private String userId;
private String name;
private String email;
private String phone;
private String passwordHash;
private UserType userType;
private List<Booking> bookings;
public User(String userId, String name, String email, String phone,
String passwordHash, UserType userType) {
this.userId = userId;
this.name = name;
this.email = email;
this.phone = phone;
this.passwordHash = passwordHash;
this.userType = userType;
this.bookings = new ArrayList<>();
}
public void addBooking(Booking booking) {
bookings.add(booking);
}
public List<Booking> getBookingHistory() {
return new ArrayList<>(bookings);
}
// Getters
public String getUserId() { return userId; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
public UserType getUserType() { return userType; }
@Override
public String toString() {
return String.format("User[%s]: %s (%s)", userId, name, email);
}
}
Movie.java
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class Movie {
private String movieId;
private String title;
private String description;
private int durationMinutes;
private Language language;
private Genre genre;
private LocalDate releaseDate;
private List<Show> shows;
public Movie(String movieId, String title, String description,
int durationMinutes, Language language, Genre genre,
LocalDate releaseDate) {
this.movieId = movieId;
this.title = title;
this.description = description;
this.durationMinutes = durationMinutes;
this.language = language;
this.genre = genre;
this.releaseDate = releaseDate;
this.shows = new ArrayList<>();
}
public void addShow(Show show) {
shows.add(show);
}
public List<Show> getShows() {
return new ArrayList<>(shows);
}
// Getters
public String getMovieId() { return movieId; }
public String getTitle() { return title; }
public String getDescription() { return description; }
public int getDurationMinutes() { return durationMinutes; }
public Language getLanguage() { return language; }
public Genre getGenre() { return genre; }
public LocalDate getReleaseDate() { return releaseDate; }
@Override
public String toString() {
return String.format("%s (%s) - %s | %d mins",
title, language, genre, durationMinutes);
}
}
City.java
import java.util.ArrayList;
import java.util.List;
public class City {
private String cityId;
private String name;
private String state;
private List<Theater> theaters;
public City(String cityId, String name, String state) {
this.cityId = cityId;
this.name = name;
this.state = state;
this.theaters = new ArrayList<>();
}
public void addTheater(Theater theater) {
theaters.add(theater);
}
public List<Theater> getTheaters() {
return new ArrayList<>(theaters);
}
// Getters
public String getCityId() { return cityId; }
public String getName() { return name; }
public String getState() { return state; }
@Override
public String toString() {
return String.format("%s, %s", name, state);
}
}
Theater.java
import java.util.ArrayList;
import java.util.List;
public class Theater {
private String theaterId;
private String name;
private String address;
private City city;
private List<Screen> screens;
public Theater(String theaterId, String name, String address, City city) {
this.theaterId = theaterId;
this.name = name;
this.address = address;
this.city = city;
this.screens = new ArrayList<>();
}
public void addScreen(Screen screen) {
screens.add(screen);
}
public List<Screen> getScreens() {
return new ArrayList<>(screens);
}
// Getters
public String getTheaterId() { return theaterId; }
public String getName() { return name; }
public String getAddress() { return address; }
public City getCity() { return city; }
@Override
public String toString() {
return String.format("%s - %s", name, address);
}
}
Screen.java
import java.util.ArrayList;
import java.util.List;
public class Screen {
private String screenId;
private String name;
private int capacity;
private Theater theater;
private List<Seat> seats;
private List<Show> shows;
public Screen(String screenId, String name, Theater theater) {
this.screenId = screenId;
this.name = name;
this.theater = theater;
this.seats = new ArrayList<>();
this.shows = new ArrayList<>();
this.capacity = 0;
}
public void addSeat(Seat seat) {
seats.add(seat);
capacity++;
}
public void addShow(Show show) {
shows.add(show);
}
public List<Seat> getSeats() {
return new ArrayList<>(seats);
}
public List<Show> getShows() {
return new ArrayList<>(shows);
}
// Getters
public String getScreenId() { return screenId; }
public String getName() { return name; }
public int getCapacity() { return capacity; }
public Theater getTheater() { return theater; }
@Override
public String toString() {
return String.format("Screen %s (%d seats)", name, capacity);
}
}
Seat.java
public class Seat {
private String seatId;
private String seatNumber;
private int rowNum;
private int colNum;
private SeatType seatType;
public Seat(String seatId, int rowNum, int colNum, SeatType seatType) {
this.seatId = seatId;
this.rowNum = rowNum;
this.colNum = colNum;
this.seatType = seatType;
this.seatNumber = generateSeatNumber(rowNum, colNum);
}
private String generateSeatNumber(int row, int col) {
char rowChar = (char) ('A' + row);
return String.format("%c%d", rowChar, col + 1);
}
// Getters
public String getSeatId() { return seatId; }
public String getSeatNumber() { return seatNumber; }
public int getRowNum() { return rowNum; }
public int getColNum() { return colNum; }
public SeatType getSeatType() { return seatType; }
@Override
public String toString() {
return String.format("%s (%s)", seatNumber, seatType);
}
}
Show and Booking Classes
Show.java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class Show {
private String showId;
private Movie movie;
private Screen screen;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Map<String, ShowSeat> showSeats; // seatId -> ShowSeat
public Show(String showId, Movie movie, Screen screen, LocalDateTime startTime) {
this.showId = showId;
this.movie = movie;
this.screen = screen;
this.startTime = startTime;
this.endTime = startTime.plusMinutes(movie.getDurationMinutes());
this.showSeats = new HashMap<>();
initializeShowSeats();
}
private void initializeShowSeats() {
for (Seat seat : screen.getSeats()) {
ShowSeat showSeat = new ShowSeat(
UUID.randomUUID().toString(),
this,
seat,
seat.getSeatType().getBasePrice()
);
showSeats.put(seat.getSeatId(), showSeat);
}
}
public List<ShowSeat> getAvailableSeats() {
List<ShowSeat> available = new ArrayList<>();
for (ShowSeat showSeat : showSeats.values()) {
if (showSeat.getStatus() == ShowSeatStatus.AVAILABLE) {
available.add(showSeat);
}
}
return available;
}
public ShowSeat getShowSeat(String seatId) {
return showSeats.get(seatId);
}
public Map<String, ShowSeat> getAllShowSeats() {
return new HashMap<>(showSeats);
}
// Getters
public String getShowId() { return showId; }
public Movie getMovie() { return movie; }
public Screen getScreen() { return screen; }
public LocalDateTime getStartTime() { return startTime; }
public LocalDateTime getEndTime() { return endTime; }
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm");
return String.format("Show: %s at %s - %s",
movie.getTitle(), screen.getName(), startTime.format(formatter));
}
}
ShowSeat.java
import java.time.LocalDateTime;
public class ShowSeat {
private String showSeatId;
private Show show;
private Seat seat;
private ShowSeatStatus status;
private double price;
private LocalDateTime lockedAt;
private String lockedBy;
public ShowSeat(String showSeatId, Show show, Seat seat, double price) {
this.showSeatId = showSeatId;
this.show = show;
this.seat = seat;
this.price = price;
this.status = ShowSeatStatus.AVAILABLE;
}
public synchronized boolean lock(String userId) {
if (status == ShowSeatStatus.AVAILABLE) {
status = ShowSeatStatus.LOCKED;
lockedBy = userId;
lockedAt = LocalDateTime.now();
return true;
}
return false;
}
public synchronized void unlock() {
if (status == ShowSeatStatus.LOCKED) {
status = ShowSeatStatus.AVAILABLE;
lockedBy = null;
lockedAt = null;
}
}
public synchronized boolean book() {
if (status == ShowSeatStatus.LOCKED) {
status = ShowSeatStatus.BOOKED;
return true;
}
return false;
}
public boolean isLockedBy(String userId) {
return status == ShowSeatStatus.LOCKED &&
lockedBy != null &&
lockedBy.equals(userId);
}
// Getters
public String getShowSeatId() { return showSeatId; }
public Show getShow() { return show; }
public Seat getSeat() { return seat; }
public ShowSeatStatus getStatus() { return status; }
public double getPrice() { return price; }
public LocalDateTime getLockedAt() { return lockedAt; }
public String getLockedBy() { return lockedBy; }
public void setStatus(ShowSeatStatus status) {
this.status = status;
}
@Override
public String toString() {
return String.format("%s - %s ($%.2f)",
seat.getSeatNumber(), status, price);
}
}
Booking.java
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Booking {
private String bookingId;
private User user;
private Show show;
private List<ShowSeat> seats;
private double totalAmount;
private BookingStatus status;
private LocalDateTime bookingTime;
private Payment payment;
private List<Ticket> tickets;
public Booking(User user, Show show, List<ShowSeat> seats) {
this.bookingId = "BK" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
this.user = user;
this.show = show;
this.seats = new ArrayList<>(seats);
this.status = BookingStatus.PENDING;
this.bookingTime = LocalDateTime.now();
this.tickets = new ArrayList<>();
calculateTotalAmount();
}
private void calculateTotalAmount() {
totalAmount = seats.stream()
.mapToDouble(ShowSeat::getPrice)
.sum();
}
public boolean confirmBooking() {
if (payment != null && payment.getStatus() == PaymentStatus.SUCCESS) {
status = BookingStatus.CONFIRMED;
// Book all seats
for (ShowSeat seat : seats) {
seat.book();
}
// Generate tickets
generateTickets();
return true;
}
return false;
}
public boolean cancelBooking() {
if (status == BookingStatus.CONFIRMED) {
status = BookingStatus.CANCELLED;
// Release seats
for (ShowSeat seat : seats) {
seat.setStatus(ShowSeatStatus.AVAILABLE);
}
// Process refund
if (payment != null) {
payment.refund();
}
return true;
}
return false;
}
private void generateTickets() {
for (ShowSeat showSeat : seats) {
Ticket ticket = new Ticket(this, showSeat);
tickets.add(ticket);
}
}
// Getters and Setters
public String getBookingId() { return bookingId; }
public User getUser() { return user; }
public Show getShow() { return show; }
public List<ShowSeat> getSeats() { return new ArrayList<>(seats); }
public double getTotalAmount() { return totalAmount; }
public BookingStatus getStatus() { return status; }
public LocalDateTime getBookingTime() { return bookingTime; }
public Payment getPayment() { return payment; }
public void setPayment(Payment payment) { this.payment = payment; }
public List<Ticket> getTickets() { return new ArrayList<>(tickets); }
@Override
public String toString() {
return String.format("Booking %s: %s - %d seats - $%.2f - %s",
bookingId, show.getMovie().getTitle(), seats.size(), totalAmount, status);
}
}
Payment and Ticket Classes
Payment.java
import java.time.LocalDateTime;
import java.util.UUID;
public class Payment {
private String paymentId;
private Booking booking;
private double amount;
private PaymentMethod method;
private PaymentStatus status;
private LocalDateTime paymentTime;
private String transactionId;
public Payment(Booking booking, PaymentMethod method) {
this.paymentId = "PAY" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
this.booking = booking;
this.amount = booking.getTotalAmount();
this.method = method;
this.status = PaymentStatus.PENDING;
}
public boolean processPayment() {
// Simulate payment processing
System.out.println("Processing payment of $" + amount + " via " + method);
try {
// Simulate payment gateway call
Thread.sleep(1000);
// Simulate success (in real system, this would be actual payment gateway response)
this.status = PaymentStatus.SUCCESS;
this.paymentTime = LocalDateTime.now();
this.transactionId = "TXN" + UUID.randomUUID().toString().substring(0, 10).toUpperCase();
System.out.println("✓ Payment successful! Transaction ID: " + transactionId);
return true;
} catch (Exception e) {
this.status = PaymentStatus.FAILED;
System.out.println("✗ Payment failed!");
return false;
}
}
public boolean refund() {
if (status == PaymentStatus.SUCCESS) {
System.out.println("Processing refund of $" + amount);
this.status = PaymentStatus.REFUNDED;
System.out.println("✓ Refund successful!");
return true;
}
return false;
}
// Getters
public String getPaymentId() { return paymentId; }
public Booking getBooking() { return booking; }
public double getAmount() { return amount; }
public PaymentMethod getMethod() { return method; }
public PaymentStatus getStatus() { return status; }
public LocalDateTime getPaymentTime() { return paymentTime; }
public String getTransactionId() { return transactionId; }
@Override
public String toString() {
return String.format("Payment %s: $%.2f via %s - %s",
paymentId, amount, method, status);
}
}
Ticket.java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
public class Ticket {
private String ticketId;
private Booking booking;
private ShowSeat showSeat;
private String qrCode;
private LocalDateTime issuedAt;
public Ticket(Booking booking, ShowSeat showSeat) {
this.ticketId = "TKT" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
this.booking = booking;
this.showSeat = showSeat;
this.qrCode = generateQRCode();
this.issuedAt = LocalDateTime.now();
}
private String generateQRCode() {
// In real system, this would generate actual QR code
return "QR-" + ticketId + "-" + showSeat.getSeat().getSeatNumber();
}
public String getTicketDetails() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm");
StringBuilder details = new StringBuilder();
details.append("========== TICKET ==========\n");
details.append("Ticket ID: ").append(ticketId).append("\n");
details.append("Booking ID: ").append(booking.getBookingId()).append("\n");
details.append("Movie: ").append(booking.getShow().getMovie().getTitle()).append("\n");
details.append("Theater: ").append(booking.getShow().getScreen().getTheater().getName()).append("\n");
details.append("Screen: ").append(booking.getShow().getScreen().getName()).append("\n");
details.append("Seat: ").append(showSeat.getSeat().getSeatNumber()).append("\n");
details.append("Show Time: ").append(booking.getShow().getStartTime().format(formatter)).append("\n");
details.append("Price: $").append(String.format("%.2f", showSeat.getPrice())).append("\n");
details.append("QR Code: ").append(qrCode).append("\n");
details.append("============================\n");
return details.toString();
}
// Getters
public String getTicketId() { return ticketId; }
public Booking getBooking() { return booking; }
public ShowSeat getShowSeat() { return showSeat; }
public String getQrCode() { return qrCode; }
public LocalDateTime getIssuedAt() { return issuedAt; }
@Override
public String toString() {
return String.format("Ticket %s: %s - Seat %s",
ticketId, booking.getShow().getMovie().getTitle(),
showSeat.getSeat().getSeatNumber());
}
}
Service Classes
SeatLockManager.java
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class SeatLockManager {
private static final int LOCK_TIMEOUT_MINUTES = 10;
private final Map<String, SeatLock> locks; // showSeatId -> SeatLock
public SeatLockManager() {
this.locks = new ConcurrentHashMap<>();
startLockCleanupTask();
}
public synchronized boolean lockSeats(List<ShowSeat> seats, String userId) {
// Check if all seats can be locked
for (ShowSeat seat : seats) {
if (!canLock(seat, userId)) {
return false;
}
}
// Lock all seats
for (ShowSeat seat : seats) {
if (seat.lock(userId)) {
SeatLock lock = new SeatLock(seat, userId);
locks.put(seat.getShowSeatId(), lock);
}
}
System.out.println("✓ Locked " + seats.size() + " seats for user: " + userId);
return true;
}
public synchronized void unlockSeats(List<ShowSeat> seats) {
for (ShowSeat seat : seats) {
seat.unlock();
locks.remove(seat.getShowSeatId());
}
System.out.println("✓ Unlocked " + seats.size() + " seats");
}
private boolean canLock(ShowSeat seat, String userId) {
if (seat.getStatus() == ShowSeatStatus.BOOKED) {
return false;
}
if (seat.getStatus() == ShowSeatStatus.LOCKED) {
// Check if locked by same user or if lock expired
SeatLock lock = locks.get(seat.getShowSeatId());
if (lock != null) {
if (lock.getUserId().equals(userId)) {
return true; // Same user can re-lock
}
if (lock.isExpired()) {
// Expired lock, release it
seat.unlock();
locks.remove(seat.getShowSeatId());
return true;
}
}
return false;
}
return true; // Available
}
public boolean isLocked(ShowSeat seat) {
return locks.containsKey(seat.getShowSeatId());
}
public void cleanExpiredLocks() {
List<String> expiredLocks = new ArrayList<>();
for (Map.Entry<String, SeatLock> entry : locks.entrySet()) {
if (entry.getValue().isExpired()) {
expiredLocks.add(entry.getKey());
}
}
for (String showSeatId : expiredLocks) {
SeatLock lock = locks.remove(showSeatId);
if (lock != null) {
lock.getShowSeat().unlock();
}
}
if (!expiredLocks.isEmpty()) {
System.out.println("Cleaned " + expiredLocks.size() + " expired locks");
}
}
private void startLockCleanupTask() {
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
cleanExpiredLocks();
}
}, 60000, 60000); // Run every minute
}
// Inner class for seat locks
private static class SeatLock {
private final String lockId;
private final ShowSeat showSeat;
private final String userId;
private final LocalDateTime lockedAt;
private final LocalDateTime expiresAt;
public SeatLock(ShowSeat showSeat, String userId) {
this.lockId = UUID.randomUUID().toString();
this.showSeat = showSeat;
this.userId = userId;
this.lockedAt = LocalDateTime.now();
this.expiresAt = lockedAt.plusMinutes(LOCK_TIMEOUT_MINUTES);
}
public boolean isExpired() {
return LocalDateTime.now().isAfter(expiresAt);
}
public ShowSeat getShowSeat() { return showSeat; }
public String getUserId() { return userId; }
}
}
BookingService.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
public class BookingService {
private final Map<String, Booking> bookings;
private final SeatLockManager lockManager;
private final Map<String, Movie> movies;
private final Map<String, Theater> theaters;
private final Map<String, Show> shows;
public BookingService() {
this.bookings = new HashMap<>();
this.lockManager = new SeatLockManager();
this.movies = new HashMap<>();
this.theaters = new HashMap<>();
this.shows = new HashMap<>();
}
// Movie search
public List<Movie> searchMovies(City city, String title) {
return movies.values().stream()
.filter(m -> m.getTitle().toLowerCase().contains(title.toLowerCase()))
.filter(m -> hasShowsInCity(m, city))
.collect(Collectors.toList());
}
public List<Movie> getMoviesByCity(City city) {
return movies.values().stream()
.filter(m -> hasShowsInCity(m, city))
.collect(Collectors.toList());
}
private boolean hasShowsInCity(Movie movie, City city) {
return movie.getShows().stream()
.anyMatch(show -> show.getScreen().getTheater().getCity().equals(city));
}
// Theater and Show search
public List<Theater> getTheatersShowingMovie(Movie movie, City city) {
return movie.getShows().stream()
.map(show -> show.getScreen().getTheater())
.filter(theater -> theater.getCity().equals(city))
.distinct()
.collect(Collectors.toList());
}
public List<Show> getShows(Movie movie, Theater theater, LocalDate date) {
return movie.getShows().stream()
.filter(show -> show.getScreen().getTheater().equals(theater))
.filter(show -> show.getStartTime().toLocalDate().equals(date))
.sorted(Comparator.comparing(Show::getStartTime))
.collect(Collectors.toList());
}
// Seat availability
public List<ShowSeat> getAvailableSeats(Show show) {
return show.getAvailableSeats();
}
public Map<SeatType, List<ShowSeat>> getSeatsGroupedByType(Show show) {
return show.getAvailableSeats().stream()
.collect(Collectors.groupingBy(ss -> ss.getSeat().getSeatType()));
}
// Booking workflow
public boolean lockSeats(Show show, List<String> seatIds, User user) {
List<ShowSeat> seatsToLock = seatIds.stream()
.map(show::getShowSeat)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (seatsToLock.size() != seatIds.size()) {
System.out.println("Some seats not found!");
return false;
}
return lockManager.lockSeats(seatsToLock, user.getUserId());
}
public Booking createBooking(User user, Show show, List<String> seatIds) {
List<ShowSeat> seats = seatIds.stream()
.map(show::getShowSeat)
.filter(Objects::nonNull)
.filter(ss -> ss.isLockedBy(user.getUserId()))
.collect(Collectors.toList());
if (seats.size() != seatIds.size()) {
System.out.println("Not all seats are locked by this user!");
return null;
}
Booking booking = new Booking(user, show, seats);
bookings.put(booking.getBookingId(), booking);
user.addBooking(booking);
System.out.println("✓ Booking created: " + booking.getBookingId());
return booking;
}
public boolean confirmBooking(Booking booking, Payment payment) {
booking.setPayment(payment);
if (payment.processPayment()) {
boolean confirmed = booking.confirmBooking();
if (confirmed) {
System.out.println("✓ Booking confirmed: " + booking.getBookingId());
return true;
}
}
// Payment failed, unlock seats
lockManager.unlockSeats(booking.getSeats());
System.out.println("✗ Booking failed: " + booking.getBookingId());
return false;
}
public boolean cancelBooking(String bookingId) {
Booking booking = bookings.get(bookingId);
if (booking != null) {
boolean cancelled = booking.cancelBooking();
if (cancelled) {
System.out.println("✓ Booking cancelled: " + bookingId);
return true;
}
}
return false;
}
// Admin operations
public void addMovie(Movie movie) {
movies.put(movie.getMovieId(), movie);
System.out.println("✓ Movie added: " + movie.getTitle());
}
public void addTheater(Theater theater) {
theaters.put(theater.getTheaterId(), theater);
System.out.println("✓ Theater added: " + theater.getName());
}
public void addShow(Show show) {
shows.put(show.getShowId(), show);
show.getMovie().addShow(show);
show.getScreen().addShow(show);
System.out.println("✓ Show added: " + show);
}
// Getters
public Booking getBooking(String bookingId) {
return bookings.get(bookingId);
}
public Movie getMovie(String movieId) {
return movies.get(movieId);
}
public Theater getTheater(String theaterId) {
return theaters.get(theaterId);
}
public Show getShow(String showId) {
return shows.get(showId);
}
}
Demo Application
BookMyShowDemo.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
public class BookMyShowDemo {
public static void main(String[] args) {
System.out.println("========== BookMyShow System Demo ==========\n");
// Initialize service
BookingService bookingService = new BookingService();
// Setup: Create cities
City mumbai = new City("C1", "Mumbai", "Maharashtra");
City bangalore = new City("C2", "Bangalore", "Karnataka");
// Setup: Create theaters
Theater pvr = new Theater("T1", "PVR Cinemas", "Phoenix Mall, Mumbai", mumbai);
Theater inox = new Theater("T2", "INOX", "Forum Mall, Bangalore", bangalore);
mumbai.addTheater(pvr);
bangalore.addTheater(inox);
// Setup: Create screens
Screen pvrScreen1 = new Screen("S1", "Screen 1", pvr);
Screen pvrScreen2 = new Screen("S2", "Screen 2", pvr);
Screen inoxScreen1 = new Screen("S3", "Screen 1", inox);
pvr.addScreen(pvrScreen1);
pvr.addScreen(pvrScreen2);
inox.addScreen(inoxScreen1);
// Setup: Create seats for screens
createSeatsForScreen(pvrScreen1);
createSeatsForScreen(pvrScreen2);
createSeatsForScreen(inoxScreen1);
// Setup: Create movies
Movie inception = new Movie("M1", "Inception",
"A mind-bending thriller", 148, Language.ENGLISH,
Genre.SCI_FI, LocalDate.of(2010, 7, 16));
Movie avengers = new Movie("M2", "Avengers: Endgame",
"Epic superhero finale", 181, Language.ENGLISH,
Genre.ACTION, LocalDate.of(2019, 4, 26));
bookingService.addMovie(inception);
bookingService.addMovie(avengers);
bookingService.addTheater(pvr);
bookingService.addTheater(inox);
// Setup: Create shows
LocalDateTime today = LocalDateTime.now();
Show show1 = new Show("SH1", inception, pvrScreen1,
today.withHour(14).withMinute(0));
Show show2 = new Show("SH2", inception, pvrScreen1,
today.withHour(18).withMinute(30));
Show show3 = new Show("SH3", avengers, pvrScreen2,
today.withHour(15).withMinute(0));
Show show4 = new Show("SH4", avengers, inoxScreen1,
today.withHour(19).withMinute(0));
bookingService.addShow(show1);
bookingService.addShow(show2);
bookingService.addShow(show3);
bookingService.addShow(show4);
// Setup: Create users
User user1 = new User("U1", "John Doe", "john@example.com",
"9876543210", "hashedpass", UserType.CUSTOMER);
User user2 = new User("U2", "Jane Smith", "jane@example.com",
"9876543211", "hashedpass", UserType.CUSTOMER);
System.out.println("========== SCENARIO 1: Search and Browse ==========\n");
// Search movies in Mumbai
System.out.println("Movies in Mumbai:");
List<Movie> moviesInMumbai = bookingService.getMoviesByCity(mumbai);
for (Movie movie : moviesInMumbai) {
System.out.println(" - " + movie);
}
// Get theaters showing Inception
System.out.println("\nTheaters showing Inception in Mumbai:");
List<Theater> theaters = bookingService.getTheatersShowingMovie(inception, mumbai);
for (Theater theater : theaters) {
System.out.println(" - " + theater);
}
// Get shows
System.out.println("\nShows for Inception at PVR today:");
List<Show> shows = bookingService.getShows(inception, pvr, LocalDate.now());
for (Show show : shows) {
System.out.println(" - " + show);
}
System.out.println("\n========== SCENARIO 2: Successful Booking ==========\n");
// Select show and view available seats
Show selectedShow = show1;
System.out.println("Selected: " + selectedShow);
System.out.println("Available seats: " + selectedShow.getAvailableSeats().size());
// Lock seats
List<String> seatsToBook = Arrays.asList(
pvrScreen1.getSeats().get(50).getSeatId(), // Middle row, good seats
pvrScreen1.getSeats().get(51).getSeatId()
);
System.out.println("\nAttempting to lock seats...");
boolean locked = bookingService.lockSeats(selectedShow, seatsToBook, user1);
if (locked) {
// Create booking
Booking booking = bookingService.createBooking(user1, selectedShow, seatsToBook);
if (booking != null) {
System.out.println("Booking created: " + booking);
// Process payment
Payment payment = new Payment(booking, PaymentMethod.UPI);
boolean confirmed = bookingService.confirmBooking(booking, payment);
if (confirmed) {
// Display tickets
System.out.println("\n--- Tickets Generated ---");
for (Ticket ticket : booking.getTickets()) {
System.out.println(ticket.getTicketDetails());
}
}
}
}
System.out.println("\n========== SCENARIO 3: Concurrent Booking Attempt ==========\n");
// User 2 tries to book same seats
System.out.println("User 2 attempting to book same seats...");
boolean locked2 = bookingService.lockSeats(selectedShow, seatsToBook, user2);
if (!locked2) {
System.out.println("✗ Cannot lock seats - already booked by another user");
}
// User 2 books different seats
List<String> differentSeats = Arrays.asList(
pvrScreen1.getSeats().get(52).getSeatId(),
pvrScreen1.getSeats().get(53).getSeatId(),
pvrScreen1.getSeats().get(54).getSeatId()
);
System.out.println("\nUser 2 booking different seats...");
locked2 = bookingService.lockSeats(selectedShow, differentSeats, user2);
if (locked2) {
Booking booking2 = bookingService.createBooking(user2, selectedShow, differentSeats);
Payment payment2 = new Payment(booking2, PaymentMethod.CREDIT_CARD);
bookingService.confirmBooking(booking2, payment2);
}
System.out.println("\n========== SCENARIO 4: Booking Cancellation ==========\n");
// User 1 cancels booking
System.out.println("User 1 requesting cancellation...");
boolean cancelled = bookingService.cancelBooking(
user1.getBookingHistory().get(0).getBookingId()
);
if (cancelled) {
System.out.println("Refund processed successfully");
}
System.out.println("\n========== SCENARIO 5: View Booking History ==========\n");
System.out.println("User 1 Booking History:");
for (Booking booking : user1.getBookingHistory()) {
System.out.println(" " + booking);
}
System.out.println("\nUser 2 Booking History:");
for (Booking booking : user2.getBookingHistory()) {
System.out.println(" " + booking);
}
System.out.println("\n========== Demo Complete ==========");
}
private static void createSeatsForScreen(Screen screen) {
// Create 10 rows with 12 seats each (120 total)
int rows = 10;
int cols = 12;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
SeatType seatType;
// Front 2 rows: REGULAR
if (row < 2) {
seatType = SeatType.REGULAR;
}
// Middle 5 rows: PREMIUM
else if (row < 7) {
seatType = SeatType.PREMIUM;
}
// Back 2 rows: VIP
else if (row < 9) {
seatType = SeatType.VIP;
}
// Last row: RECLINER
else {
seatType = SeatType.RECLINER;
}
String seatId = screen.getScreenId() + "-R" + row + "C" + col;
Seat seat = new Seat(seatId, row, col, seatType);
screen.addSeat(seat);
}
}
System.out.println("Created " + screen.getCapacity() + " seats for " + screen.getName());
}
}
Design Patterns & Best Practices
Design Patterns Used
1. Singleton Pattern
- SeatLockManager: Ensures only one instance manages all seat locks
- Prevents conflicts in concurrent booking scenarios
2. Factory Pattern
- Could be extended for creating different types of users, payments, or tickets
- Example:
UserFactory,PaymentFactory
3. Strategy Pattern
- PaymentMethod: Different payment strategies (UPI, Card, etc.)
- Allows easy addition of new payment methods
4. Observer Pattern
- Could be implemented for:
- Notifying users about booking confirmation
- Sending email/SMS notifications
- Updating booking analytics
5. State Pattern
- BookingStatus: Manages booking lifecycle (PENDING → CONFIRMED → CANCELLED)
- PaymentStatus: Manages payment states
- ShowSeatStatus: Manages seat states (AVAILABLE → LOCKED → BOOKED)
6. Repository Pattern
- Service classes act as repositories for data access
- Separates business logic from data storage
SOLID Principles
Single Responsibility Principle (SRP)
- Each class has one clear responsibility:
User: User managementBooking: Booking logicPayment: Payment processingSeatLockManager: Seat locking only
Open/Closed Principle (OCP)
- Enums for extensibility (PaymentMethod, SeatType)
- Easy to add new payment methods or seat types
- Service classes can be extended without modification
Liskov Substitution Principle (LSP)
- All entities can be substituted with their base types
- Inheritance hierarchies maintain behavior contracts
Interface Segregation Principle (ISP)
- Classes don't depend on interfaces they don't use
- Focused, minimal interfaces
Dependency Inversion Principle (DIP)
- High-level modules (BookingService) depend on abstractions
- Could be improved with interfaces for services
Concurrency & Seat Locking
Concurrency Challenges
Problem: Double Booking
When multiple users try to book the same seat simultaneously:
Time User A User B
t1 Check seat status
t2 Seat is available Check seat status
t3 Seat is available
t4 Book seat Book seat
❌ BOTH BOOKED!
Solution: Pessimistic Locking
Seat Lock Mechanism
-
Lock Before Booking
- User selects seats → Seats are locked for 10 minutes
- Other users cannot lock the same seats
- If payment not completed in 10 minutes → Lock expires
-
Synchronized Methods
public synchronized boolean lock(String userId) {
if (status == ShowSeatStatus.AVAILABLE) {
status = ShowSeatStatus.LOCKED;
lockedBy = userId;
lockedAt = LocalDateTime.now();
return true;
}
return false;
} -
Lock Expiration
- Background thread cleans expired locks every minute
- Prevents indefinite seat blocking
- Releases seats if user abandons booking
Thread Safety
- ConcurrentHashMap: Used for storing locks
- Synchronized methods: Prevent race conditions
- Atomic operations: Lock/unlock happen atomically
Booking Flow with Locking
1. User selects seats
↓
2. System locks seats (10-min timeout)
↓
3. User proceeds to payment
↓
4. Payment processed
↓
5a. SUCCESS → Seats booked, lock removed
5b. FAILURE → Seats unlocked, available again
5c. TIMEOUT → Lock expires, seats released
Edge Cases & Extensions
Edge Cases Handled
1. Expired Locks
- Background cleanup task releases expired locks
- Seats become available again after timeout
2. Payment Failure
- Locked seats are released immediately
- User can retry booking
3. Concurrent Lock Attempts
- Only first user gets the lock
- Others receive failure notification
4. Same User Re-locking
- User can extend their own lock
- Prevents accidental seat loss during booking
5. Show Time Conflicts
- Validation can be added to prevent overlapping shows
- Buffer time between shows for cleaning
6. Cancellation Window
- Can add time-based cancellation rules
- Full refund if cancelled 24 hours before show
- Partial refund if cancelled later
Potential Extensions
1. Dynamic Pricing
public class DynamicPricingService {
public double calculatePrice(ShowSeat seat, LocalDateTime showTime) {
double basePrice = seat.getPrice();
// Weekend pricing
if (isWeekend(showTime)) {
basePrice *= 1.2;
}
// Prime time (evening shows)
if (isPrimeTime(showTime)) {
basePrice *= 1.15;
}
// High demand (> 80% occupancy)
if (getOccupancyRate(seat.getShow()) > 0.8) {
basePrice *= 1.25;
}
return basePrice;
}
}
2. Seat Recommendations
public class SeatRecommendationService {
public List<ShowSeat> recommendSeats(Show show, int numSeats) {
// Find best available seats based on:
// - Center position
// - Row preference (middle rows)
// - Contiguous seats
// - User preferences (aisle, window)
}
}
3. Loyalty Program
public class LoyaltyProgram {
private Map<String, Integer> userPoints;
public void awardPoints(User user, Booking booking) {
int points = (int) (booking.getTotalAmount() * 0.1);
userPoints.merge(user.getUserId(), points, Integer::sum);
}
public double applyDiscount(User user, double amount) {
int points = userPoints.getOrDefault(user.getUserId(), 0);
double discount = Math.min(points * 0.1, amount * 0.2); // Max 20% discount
return amount - discount;
}
}
4. Food & Beverage
public class FoodOrder {
private String orderId;
private Booking booking;
private List<FoodItem> items;
private double totalAmount;
public void addToBooking(Booking booking) {
// Add food order to booking
// Deliver to seat during show
}
}
5. Notifications
public interface NotificationService {
void sendBookingConfirmation(User user, Booking booking);
void sendCancellationConfirmation(User user, Booking booking);
void sendReminder(User user, Booking booking, int hoursBeforeShow);
}
public class EmailNotificationService implements NotificationService {
// Email implementation
}
public class SMSNotificationService implements NotificationService {
// SMS implementation
}
6. Analytics Dashboard
public class AnalyticsService {
public BookingAnalytics getAnalytics(LocalDate startDate, LocalDate endDate) {
// Revenue by movie
// Popular time slots
// Occupancy rates
// User demographics
// Cancellation rates
return new BookingAnalytics();
}
}
Summary
Key Design Decisions
- Seat Locking: Prevents double booking with 10-minute timeout
- Three-State Seat Status: AVAILABLE → LOCKED → BOOKED
- Separation of Concerns: Services handle business logic, entities hold data
- Enum-Based Configuration: Easy to extend seat types, payment methods, etc.
- Thread Safety: Synchronized methods and concurrent collections
Scalability Considerations
Database Design
- Indexing: On movieId, theaterId, showTime, userId
- Partitioning: Shows by date, bookings by user
- Caching: Popular movies, theater listings, available seats
Distributed Systems
- Load Balancer: Distribute booking requests
- Redis: For seat locking across multiple servers
- Message Queue: For async operations (notifications, analytics)
- CDN: For static content (movie posters, theater images)
Performance Optimization
- Read Replicas: For search queries
- Write-Heavy Optimization: Booking confirmations
- Connection Pooling: Database connections
- Caching Strategy:
- Movie list: 1 hour TTL
- Show timings: 15 minutes TTL
- Seat availability: Real-time (no cache)
Testing Strategy
- Unit Tests: Individual class methods
- Integration Tests: Service interactions
- Concurrency Tests: Multiple simultaneous bookings
- Load Tests: 1000s of concurrent users
- Edge Case Tests: Timeouts, failures, race conditions
Future Enhancements
- Mobile app integration
- Social features (group bookings, sharing)
- AI-based seat recommendations
- Virtual reality seat preview
- Blockchain-based ticketing
- Integration with streaming platforms
- Multi-language support
- Accessibility features (wheelchair seats, audio descriptions)